//
//  SerialPortReader.m
//
//	Serial Remote for Mac - Control mac actions via serial commands
//	Copyright (C) 2010  Jeremiah McConnell
//
//	This program is free software: you can redistribute it and/or modify
//	it under the terms of the GNU General Public License as published by
//	the Free Software Foundation, either version 3 of the License, or
//	(at your option) any later version.
//
//	This program is distributed in the hope that it will be useful,
//	but WITHOUT ANY WARRANTY; without even the implied warranty of
//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//	GNU General Public License for more details.
//
//	You should have received a copy of the GNU General Public License
//	along with this program.  If not, see <http://www.gnu.org/licenses/>.
//

#import "SerialPortReader.h"
#import "SerialPortSelectionData.h"

#import "CommandExecutionController.h"

#import "AMSerialPort.h"
#import "AMSerialPortList.h"
#import "AMSerialPortAdditions.h"

static SerialPortReader *SerialPortReaderSingleton = nil;

// Serial Port
AMSerialPort *port = nil;

// Serial command buffer
NSString *currentBuffer = @"";

// Monitor Object
id<SerialPortMonitor> monitor = nil;

// Executor
CommandExecutionController *executor = nil;

@implementation SerialPortReader

@synthesize delimiter;

#pragma mark -
#pragma mark Class Methods

+ (SerialPortReader *)sharedSerialPortReader
{
	@synchronized(self) {
		if (SerialPortReaderSingleton == nil) {
			SerialPortReaderSingleton = [[self alloc] init];
		}
	}
	return SerialPortReaderSingleton;
}

#pragma mark -
#pragma mark Processing Methods

- (void)setDelimiter:(NSString*)newDelimiter
{
	NSString *newStr = [newDelimiter stringByReplacingOccurrencesOfString:@"\\r" withString:@"\r"];
	newStr = [newStr stringByReplacingOccurrencesOfString:@"\\n" withString:@"\n"];
	delimiter = newStr;
}

- (void)addToBuffer:(NSString*)string
{
	currentBuffer = [currentBuffer stringByAppendingString:string];
	
	// Process the buffer if I have a carrage return
	NSRange crLocation = [currentBuffer rangeOfString:delimiter];
	if (crLocation.location != NSNotFound) {
		
		NSString *cmd = [currentBuffer substringToIndex:crLocation.location];
		currentBuffer = [currentBuffer substringFromIndex:crLocation.location + [delimiter length]];
		
		[executor execute:cmd];
	}
}

#pragma mark -
#pragma mark Initialization Methods

- (id)init
{
	if ((self = [super init])) {
		// follow changes to port and rate changes
		SerialPortSelectionData *portData = [SerialPortSelectionData sharedPortSlectionData];
		[portData addObserver:self forKeyPath:@"selectedPort" options:NSKeyValueObservingOptionNew context:NULL];
		[portData addObserver:self forKeyPath:@"selectedPortRate" options:NSKeyValueObservingOptionNew context:NULL];
		[self bind:@"delimiter" toObject:[NSUserDefaultsController sharedUserDefaultsController] withKeyPath:@"values.commandDelimiter" options:nil];
		
		executor = [CommandExecutionController sharedCommandExecutionController];
	}
	
	return self;
}

#pragma mark -
#pragma mark Serial Port Methods


// Setup a serial port object
- (void)setPort:(AMSerialPort *)newPort
{
    if (newPort != port) {
		port = newPort;
    }
}


// Initialize the selected serial port
- (void)initPort:(NSString *)deviceName
{
	if (![deviceName isEqualToString:[port bsdPath]]) {
		[port close];
		
		[self setPort:[[AMSerialPort alloc] init:deviceName withName:deviceName type:(NSString*)CFSTR(kIOSerialBSDModemType)]];
		
		// register as self as delegate for port
		[port setDelegate:self];
		
		// open port - may take a few seconds ...
		if ([port open]) {
			
			// listen for data in a separate thread
			[port readDataInBackground];
			
		} else { // an error occured while creating port
			[self setPort:nil];
		}
	}
}

- (void)serialPortReadData:(NSDictionary *)dataDictionary
{
	// this method is called if data arrives 
	// @"data" is the actual data, @"serialPort" is the sending port
	AMSerialPort *sendPort = [dataDictionary objectForKey:@"serialPort"];
	NSData *data = [dataDictionary objectForKey:@"data"];
	
	if ([data length] > 0) {
		NSString *text = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
		
		// Notify the monitor of my new data
		if (monitor != nil) {
			[monitor monitorData:text];
		}
		
		[self addToBuffer:text];
		
		// continue listening
		[sendPort readDataInBackground];
		
	}
}

#pragma mark -
#pragma mark Control Methods

- (void)start
{
	SerialPortSelectionData *portData = [SerialPortSelectionData sharedPortSlectionData];
	[self initPort:[portData selectedPort]];
	[port setSpeed:[[portData selectedPortRate] integerValue]];
}

- (void)stop
{
	[port close];
}

- (void)setMonitor:(id<SerialPortMonitor>)newMonitor
{
	monitor = newMonitor;
}


#pragma mark -
#pragma mark KVO Methods

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
	if ([keyPath isEqualToString:@"selectedPortRate"]) {
		NSString *newSpeed = [change valueForKey:NSKeyValueChangeNewKey];
		[port setSpeed:[newSpeed integerValue]];
	}
	else if ([keyPath isEqualToString:@"selectedPort"]) {
		[self stop];
		[self start];
	}
	else if ([keyPath isEqualToString:@"commandDelimiter"]) {
		[self setDelimiter:[change valueForKey:NSKeyValueChangeNewKey]];
	}
	
}


@end
